package com.example.gateway.filter;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.example.common.core.constants.CacheConstants;
import com.example.common.core.constants.HttpConstants;
import com.example.common.core.domain.LoginUser;
import com.example.common.core.domain.Result;
import com.example.common.core.enums.Identity;
import com.example.common.core.enums.ResultCode;

import com.example.common.core.utils.JwtUtils;
import com.example.gateway.utils.IgnoreWhiteProperties;
import com.example.redis.service.RedisService;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.List;

@Slf4j
@Component
public class AutoFilter implements GlobalFilter, Ordered {
    @Autowired
    private IgnoreWhiteProperties ignoreWhiteProperties;

    @Value("${jwt.secret}")
    private String secret;

    @Autowired
    private RedisService redisService;
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //验证登陆的逻辑
        //1.获取拦截到的请求
        ServerHttpRequest request = exchange.getRequest();
        //2.从请求中获取请求路径
        String url = request.getURI().getPath();

        //3.判断该路径是否需要拦截(比如登陆就不需要拦截)
        //在配置文件中设置需要拦截的路径
        //从配置文件中获取需要拦截的所有路径，判断当前该请求是否在其中
        //如果在其中，说明该路径不需要拦截，直接跳过
        //如果不在其中，则需要拦截，并验证身份
        System.out.println(ignoreWhiteProperties.getWhites());
        if (matches(url,ignoreWhiteProperties.getWhites())){
            //放行
            return chain.filter(exchange);
        }
        //4.验证身份
        //先获取用户发来的token
        String token = getToken(request);
        //判空
        if (StrUtil.isEmpty(token)){
            return unauthorizedResponse(exchange,"令牌不能为空");
        }
        //开始验证令牌的数据部分
        //解析出数据部分
        Claims claims;
        try {
            claims = JwtUtils.parseToken(token,secret);
            //判空
            if (claims == null){
                return unauthorizedResponse(exchange,"令牌校验已过期/验证不正确");
            }
        }catch (Exception e){
            return unauthorizedResponse(exchange,"令牌校验已过期/验证不正确");
        }
        //获取数据部分中的userKey
        String userKey = JwtUtils.getUserKey(claims);
        //然后去redis数据库，验证这个userkey是否存在
        boolean isLogin = redisService.hasKey("login_tokens:" + userKey);
        if (!isLogin){
            return unauthorizedResponse(exchange,"登录状态已过期");
        }
        //如果登录状态没有过期，则开始验证redis数据库中存储的userKey对应的值
        //先生成userKey在redis对应的key，有前缀
        String key = getTokenKey(userKey);
        //查询key对应的值，并且进行反序列化
        LoginUser user = redisService.getCacheObject(key,LoginUser.class);
        //判断当前拦截到的请求路径中是system还是friend的
        //如果是system的，则要验证是不是管理员登录，是才放行
        if (url.contains(HttpConstants.SYSTEM_URL_PREFIX) &&
                !Identity.ADMIN.getValue().equals(user.getIdentity())) {
            return unauthorizedResponse(exchange, "令牌验证失败");
        }
        //如果是friend的，则要验证是不是普通用户登录，是才放行
        if (url.contains(HttpConstants.FRIEND_URL_PREFIX) &&
                !Identity.ORDINARY.getValue().equals(user.getIdentity())) {
            return unauthorizedResponse(exchange, "令牌验证失败");
        }
        //上面都通过验证了，放行
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }

    //匹配路径
    public boolean matches(String url, List<String> patternList){
        if (StrUtil.isEmpty(url) || CollectionUtils.isEmpty(patternList)){
            return false;
        }
        for (String pattern : patternList){
            if (isMatch(url,pattern)){
                return true;
            }
        }
        return false;
    }

    //判断两个路径是否一样(这里是根据特殊的匹配规则)
    public boolean isMatch(String url,String pattern){
        AntPathMatcher matcher = new AntPathMatcher();
        return matcher.match(pattern,url);
    }

    //从请求头中获取token
    private String getToken(ServerHttpRequest request) {
        String token =
                request.getHeaders().getFirst(HttpConstants.AUTHENTICATION);
//        token.startsWith("B");
//        System.out.println(token.startsWith("Bearer"));
        // 如果前端设置了令牌前缀，则裁剪掉前缀
            if (StrUtil.isNotEmpty(token) &&
                    token.startsWith(HttpConstants.PREFIX)) {
                token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
            }
        return token;
    }

    //处理异常时候
    private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String
            msg) {
        log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath());
        return webFluxResponseWriter(exchange.getResponse(), msg,
                ResultCode.FAILED_UNAUTHORIZED.getCode());
    }

    //拼装webflux模型响应
    private Mono<Void> webFluxResponseWriter(ServerHttpResponse response,
                                             String msg, int code) {
        response.setStatusCode(HttpStatus.OK);
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE,
                MediaType.APPLICATION_JSON_VALUE);
        Result<?> result = Result.fail(code, msg);
        DataBuffer dataBuffer =
                response.bufferFactory().wrap(JSON.toJSONString(result).getBytes());
        return response.writeWith(Mono.just(dataBuffer));
    }

    private String getTokenKey(String token) {
        return CacheConstants.LOGIN_TOKEN_KEY + token;
    }
}
